《Effective JavaScript》

Chapter 1: Accustoming Yourself to JavaScript

Item 1: Know Which JavaScript You Are Using

Item 2: Understand JavaScript’s Floating-Point Numbers

Item 3: Beware of Implicit Coercions

Item 4: Prefer Primitives to Object Wrappers

1
2
3
var s1 = new String("hello");
var s2 = new String("hello");
s1 === s2; // false

Since each String object is a separate object, it is only ever equal to itself.

Things to Remember

  • Object wrappers for primitive types do not have the same behavior as their primitive values when compared for equality.
  • Getting and setting properties on primitives implicitly creates object wrappers.

Item 5: Avoid using == with Mixed Types

Things to Remember

  • The == operator applies a confusing set of implicit coercions when its arguments are of different types.
  • Use === to make it clear to your readers that your comparison does not involve any implicit coercions.
  • Use your own explicit coercions when comparing values of different types to make your program’s behavior clearer.

Item 6: Learn the Limits of Semicolon Insertion

Things to Remember

  • Semicolons are only ever inferred before a }, at the end of a line, or at the end of a program.
  • Semicolons are only ever inferred when the next token cannot be parsed.
  • Never omit a semicolon before a statement beginning with (, [, +, -, or /.
  • When concatenating scripts, insert semicolons explicitly between scripts.
  • Never put a newline before the argument to return, throw, break, continue, ++, or –.
  • Semicolons are never inferred as separators in the head of a for loop or as empty statements.

Item 7: Think of Strings As Sequences of 16-Bit Code Units

Today there are multiple standard encodings of Unicode, the most popular of which are UTF-8, UTF-16, and UTF-32.

Things to Remember

  • JavaScript strings consist of 16-bit code units, not Unicode code points.
  • Unicode code points 216 and above are represented in JavaScript by two code units, known as a surrogate pair.
  • Surrogate pairs throw off string element counts, affecting length, charAt, charCodeAt, and regular expression patterns such as “.”.
  • Use third-party libraries for writing code point-aware string manipulation.
  • Whenever you are using a library that works with strings, con-sult the documentation to see how it handles the full range of code points.

Chapter 2: Variable Scope

Item 8: Minimize Use of the Global Object

Since the global object provides a dynamic reflection of the global environment, you can use it to query a running environment to detect which features are available on the platform.

1
2
3
4
5
6
if (!this.JSON) {
this.JSON = {
parse: ...,
stringify: ...
};
}

Things to Remember

  • Avoid declaring global variables.
  • Declare variables as locally as possible.
  • Avoid adding properties to the global object.
  • Use the global object for platform feature detection.

Item 9: Always Declare Local Variables

Things to Remember

  • Always declare new local variables with var.
  • Consider using lint tools to help check for unbound variables.

Item 10: Avoid with

Item 11: Get Comfortable with Closures

Things to Remember

  • Functions can refer to variables defined in outer scopes.
  • Closures can outlive the function that creates them.
  • Closures internally store references to their outer variables, and can both read and update their stored variables.

Item 12: Understand Variable Hoisting

A good way to think about the behavior of JavaScript variable decla-rations is to understand them as consisting of two parts: a declara-tion and an assignment.

Things to Remember

  • Variable declarations within a block are implicitly hoisted to the top of their enclosing function.
  • Redeclarations of a variable are treated as a single variable.
  • Consider manually hoisting local variable declarations to avoid confusion.

Item 13: Use Immediately Invoked Function Expressions to Create Local Scopes

Things to Remember

  • Understand the difference between binding and assignment.
  • Closures capture their outer variables by reference, not by value.
  • Use immediately invoked function expressions (IIFEs) to create local scopes.
  • Be aware of the cases where wrapping a block in an IIFE can change its behavior.

Item 14: Beware of Unportable Scoping of Named Function Expressions

The real usefulness of named function expressions, though, is for debugging. Most modern JavaScript environments produce stack traces for Error objects, and the name of a function expression is typically used for its entry in a stack trace. Debuggers with facilities for inspecting the stack typically make similar use of named function expressions.

Item 15: Beware of Unportable Scoping of Block-Local Function Declarations

Item 16: Avoid Creating Local Variables with eval

Things to Remember

  • Avoid creating variables with eval that pollute the caller’s scope.
  • If eval code might create global variables, wrap the call in a nested function to prevent scope pollution.

Item 17: Prefer Indirect eval to Direct eval

Chapter 3: Working with Functions

Item 18: Understand the Difference between Function, Method, and Constructor Calls

Calling a method as a function rarely does anything useful if the method depends on this, since there is no reason to expect the global object to match the expectations that the method has of the object it is called on.

Things to Remember

  • Method calls provide the object in which the method property is looked up as their receiver.
  • Function calls provide the global object (or undefined for strict functions) as their receiver. Calling methods with function call syntax is rarely useful.
  • Constructors are called with new and receive a fresh object as their receiver.

Item 19: Get Comfortable Using Higher-Order Functions

高阶函数的定义

Higher-order functions are nothing more than functions that take other functions as arguments or return functions as their result.

抽象高阶函数的优点

This allows you to fix any bugs in the logic just once, instead of having to hunt for every instance of the coding pattern spread throughout your program. If you find you need to optimize the efficiency of the operation, you again only have one place where you need to change anything. Finally, giving a clear name such as buildString to the abstraction makes it clearer to someone reading the code what the code does, without having to decode the details of the implementation.

Things to Remember

  • Higher-order functions are functions that take other functions as arguments or return functions as their result.
  • Familiarize yourself with higher-order functions in existing libraries.
  • Learn to detect common coding patterns that can be replaced by higher-order functions.

Item 20: Use call to Call Methods with a Custom Receiver

使用 call 方法调用 function

Luckily, functions come with a built-in call method for providing a custom receiver. Invoking a function via its call method:

1
f.call(obj, arg1, arg2, arg3);

behaves similarly to calling it directly:、

1
f(arg1, arg2, arg3);

except that the first argument provides an explicit receiver object.

Things to Remember

  • Use the call method to call a function with a custom receiver.
  • Use the call method for calling methods that may not exist on a given object.
  • Use the call method for defining higher-order functions that allow clients to provide a receiver for the callback.

Item 21: Use apply to Call Functions with Different Numbers of Arguments

案例

Imagine that someone provides us with a function that calculates the average of any number of values:

1
2
3
4
average(1, 2, 3); // 2
average(1); // 1
average(3, 1, 4, 1, 5, 9, 2, 6, 5); // 4
average(2, 7, 1, 8, 2, 8, 1, 8); // 4.625

Things to Remember

  • Use the apply method to call variadic functions with a computed array of arguments.
  • Use the first argument of apply to provide a receiver for variadic methods.

Item 22: Use arguments to Create Variadic Functions

A good rule of thumb is that whenever you provide a variable-arity function for convenience, you should also provide a fixed-arity version that takes an explicit array.

Things to Remember

  • Use the implicit arguments object to implement variable-arity functions.
  • Consider providing additional fixed-arity versions of the variadic functions you provide so that your consumers don’t need to use the apply method.

Item 23: Never Modify the arguments Object

As a consequence, it is much safer never to modify the arguments object. This is easy enough to avoid by first copying its elements to a real array. A simple idiom for implementing the copy is:

1
var args = [].slice.call(arguments);

注意点

The arguments object is not a copy of the function’s arguments

Things to Remember

  • Never modify the arguments object.
  • Copy the arguments object to a real array using [].slice.call(arguments) before modifying it.

Item 24: Use a Variable to Save a Reference to arguments

案例

Imagine we wish to write a convenience function that takes an arbitrary number of arguments and builds an iterator for those values:

1
2
3
4
var it = values(1, 4, 1, 4, 2, 1, 3, 5, 6);
it.next(); // 1
it.next(); // 4
it.next(); // 1

Things to Remember

  • Be aware of the function nesting level when referring to arguments.
  • Bind an explicitly scoped reference to arguments in order to refer to it from nested functions.

Item 25: Use bind to Extract Methods with a Fixed Receiver

A function’s receiver is determined by how it is called

Things to Remember

  • Beware that extracting a method does not bind the method’s receiver to its object.
  • When passing an object’s method to a higher-order function, use an anonymous function to call the method on the appropriate receiver.
  • Use bind as a shorthand for creating a function bound to the appropriate receiver.

Item 26: Use bind to Curry Functions

Things to Remember

  • Use bind to curry a function, that is, to create a delegating function with a fixed subset of the required arguments.
  • Pass null or undefined as the receiver argument to curry a function that ignores its receiver.

Item 27: Prefer Closures to Strings for Encapsulating Code

Things to Remember

  • Never include local references in strings when sending them to APIs that execute them with eval.
  • Prefer APIs that accept functions to call rather than strings to eval.

Item 28: Avoid Relying on the toString Method of Functions

Things to Remember

  • JavaScript engines are not required to produce accurate reflections of function source code via toString.
  • Never rely on precise details of function source, since different engines may produce different results from toString.
  • The results of toString do not expose the values of local variables stored in a closure.
  • In general, avoid using toString on functions.

Item 29: Avoid Nonstandard Stack Inspection Properties

Things to Remember

  • Avoid the nonstandard arguments.caller and arguments.callee, because they are not reliably portable.
  • Avoid the nonstandard caller property of functions, because it does not reliably contain complete information about the stack.

Chapter 4: Objects and Prototypes

Item 30: Understand the Difference between prototype, getPrototypeOf, and __proto__

区别

  • C.prototype is used to establish the prototype of objects created by new C().
  • Object.getPrototypeOf(obj) is the standard ES5 mechanism for retrieving obj’s prototype object.
  • obj.__proto__ is a nonstandard mechanism for retrieving obj’s prototype object.

Things to Remember

  • C.prototype determines the prototype of objects created by new C().
  • Object.getPrototypeOf(obj) is the standard ES5 function for retrieving the prototype of an object.
  • obj.__proto__ is a nonstandard mechanism for retrieving the prototype of an object.
  • A class is a design pattern consisting of a constructor function and an associated prototype.

Item 31: Prefer Object.getPrototypeOf to __proto__

Things to Remember

  • Prefer the standards-compliant Object.getPrototypeOf to the non-standard __proto__ property.
  • Implement Object.getPrototypeOf in non-ES5 environments that support __proto__.

Item 32: Never Modify __proto__

The special __proto__ property provides an additional power that Object.getPrototypeOf does not: the ability to modify an object’s prototype link.

Another reason to avoid modifying __proto__ is performance.

Item 33: Make Your Constructors new-Agnostic

Item 34: Store Methods on Prototypes

Things to Remember

  • Storing methods on instance objects creates multiple copies of the functions, one per instance object.
  • Prefer storing methods on prototypes over storing them on instance objects.

Item 35: Use Closures to Store Private Data

Item 36: Store Instance State Only on Instance Objects

Item 37: Recognize the Implicit Binding of this

Every function has an implicit binding of this, whose value is determined when the function is called.

Item 38: Call Superclass Constructors from Subclass Constructors

Item 39: Never Reuse Superclass Property Names

Item 40: Avoid Inheriting from Standard Classes

Item 41: Treat Prototypes As an Implementation Detail

Item 42: Avoid Reckless Monkey-Patching

Things to Remember

  • Avoid reckless monkey-patching.
  • Document any monkey-patching performed by a library.
  • Consider making monkey-patching optional by performing the modifications in an exported function.
  • Use monkey-patching to provide polyfills for missing standard APIs.

Chapter 5: Arrays and Dictionaries

Item 43: Build Lightweight Dictionaries from Direct Instances of Object

Item 44: Use null Prototypes to Prevent Prototype Pollution

Things to Remember

  • In ES5, use Object.create(null) to create prototype-free empty objects that are less susceptible to pollution.
  • In older environments, consider using { __proto__: null }.
  • But beware that __proto__ is neither standard nor entirely portable and may be removed in future JavaScript environments.
  • Never use the name “proto“ as a dictionary key since some environments treat this property specially.

Item 45: Use hasOwnProperty to Protect Against Prototype Pollution

Item 46: Prefer Arrays to Dictionaries for Ordered Collections

Item 47: Never Add Enumerable Properties to Object.prototype

Item 48: Avoid Modifying an Object during Enumeration

Things to Remember

  • Make sure not to modify an object while enumerating its properties with a for...in loop.
  • Use a while loop or classic for loop instead of a for…in loop when iterating over an object whose contents might change during the loop.
  • For predictable enumeration over a changing data structure, con-sider using a sequential data structure such as an array instead of a dictionary object.

Item 49: Prefer for Loops to for…in Loops for Array Iteration

循环操作优于函数的地方

There is one thing that loops tend to do better than iteration functions: abnormal control flow operations such as break and continue.

For example, it would be awkward to attempt to implement takeWhile using forEach:

1
2
3
4
5
6
7
8
9
10
function takeWhile(a, pred) {
var result = [];
a.forEach(function(x, i) {
if (!pred(x)) {
// ?
}
result[i] = x;
});
return result;
}

We could use an internal exception to implement the early termina-tion of the loop, but this would be awkward and likely inefficient:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function takeWhile(a, pred) {
var result = [];
var earlyExit = {}; // unique value signaling loop break
try {
a.forEach(function(x, i) {
if (!pred(x)) {
throw earlyExit;
}
result[i] = x;
});
} catch (e) {
if (e !== earlyExit) { // only catch earlyExit
throw e;
}
}
return result;
}

Item 50: Prefer Iteration Methods to Loops

Item 51: Reuse Generic Array Methods on Array-Like Objects

Item 52: Prefer Array Literals to the Array Constructor

Chapter 6: Library and API Design

Item 53: Maintain Consistent Conventions

Item 54: Treat undefined As “No Value”

But beware: Truthiness is not always a safe test. If a function should accept the empty string as a legal value, a truthy test will override the empty string and replace it with the default value. Similarly, a function that accepts a number should not use a truthy test if it allows 0 (or NaN, although it’s less common) as an acceptable value.

Item 55: Accept Options Objects for Keyword Arguments

优点

Another benefit of options objects is that any of the arguments can be optional, and a caller can provide any subset of the optional arguments.

以更优雅的方式设计函数

If there are one or two required arguments, it’s better to keep them separate from the options object:

1
2
3
4
5
6
7
8
9
var alert = new Alert(app, message, {
width: 150, height: 100,
title: "Error",
titleColor: "blue",
bgColor: "white",
textColor: "black",
icon: "error",
modal: true
});

Things to Remember

  • Use options objects to make APIs more readable and memorable.
  • The arguments provided by an options object should all be treated as optional.
  • Use an extend utility function to abstract out the logic of extracting values from options objects.

Item 56: Avoid Unnecessary State

Things to Remember

  • Prefer stateless APIs where possible.
  • When providing stateful APIs, document the relevant state that each operation depends on.

Item 57: Use Structural Typing for Flexible Interfaces

Item 58: Distinguish between Array and Array-Like

判断数组

Use ES5’s Array.isArray to test for true arrays.

In environments that don’t support ES5, you can use the standard Object.prototype.toString method to test whether an object is an array:

1
2
3
4
var toString = Object.prototype.toString;
function isArray(x) {
return toString.call(x) === "[object Array]";
}

Item 59: Avoid Excessive Coercion

Item 60: Support Method Chaining

Things to Remember

  • Use method chaining to combine stateless operations.
  • Support method chaining by designing stateless methods that produce new objects.
  • Support method chaining in stateful methods by returning this.

Chapter 7: Concurrency

Item 61: Don’t Block the Event Queue on I/O

The single most important rule of concurrent JavaScript is never to use any blocking I/O APIs in the middle of an application’s event queue.

Things to Remember

  • Asynchronous APIs take callbacks to defer processing of expensive operations and avoid blocking the main application.
  • JavaScript accepts events concurrently but processes event handlers sequentially using an event queue.
  • Never use blocking I/O in an application’s event queue.

Item 62: Use Nested or Named Callbacks for Asynchronous Sequencing

Item 63: Be Aware of Dropped Errors

Item 64: Use Recursion for Asynchronous Loops

Item 65: Don’t Block the Event Queue on Computation

Item 66: Use a Counter to Perform Concurrent Operations

Item 67: Never Call Asynchronous Callbacks Synchronously

Item 68: Use Promises for Cleaner Asynchronous Logic